3.1-DOM和事件
Create by fall on 12 May 2020 Recently revised in 25 Apr 2024
DOM
文档对象模型 Document Object Model
浏览器中,document
指的是从 <html>
到 </html>
结束的部分,也是文档展示的部分
元素:element,一个典型的元素包含标签、文本、属性。我们一般会通过获取到元素(element)然后对 DOM 进行操作。
Element 继承了 Node 和 EventTarget 对象
<div id="foo" name="bar" class="cheer">
it's fall
</div>
在上方的内容中,节点类型为:
- 标签节点(tag):
<div>
、</div>
- 文本节点(text):
it's fall
等文本内容 - 属性节点(attribute):
id="box1"
获取 element
- 通过 id 获取对象:
document.getElementById()
- 通过 class,类名获取对象:
document.getElementsByClass()
- 通过 tag,节点类型获取对象:
document.getElementsByTagName()
- 通过 name,获取节点属性:
document.getElementByName()
- 通过选择器,获取第一个选择到的对象:
document.querySelector()
- 通过选择器,选择对象:
document.querySelectorAll()
通过 id 和 querySelector
获得的是单个对象,其它的获取方式都是获取的列表
// 获取 id 为 foo 的节点
var domFoo = document.getElementById("foo")
// 获取 document,或者是其它节点内部的节点
var divList = document.getElementsByTagName("div")
// 获取 document,或者是其它节点内部的节点
var divCherrList = document.getElementsByClassName("cheer")
// 只能使用 document 进行获取
var nameBar = document.getElementByName("")
// 找到符合条件的第一个元素节点
var query = document.querySeletor("#foo.bar")
// 找到符合条件的所有元素节点
var queryList = document.querSeletorAll(".bar")
// 分别输出 id、类名、dom 设置的宽度
console.log(domFoo.id, domFoo.className, domFoo.style.width)
domFoo.id = "ssu"// 设置 id
domFoo.className = 'box4'// 设置
domFoo.style.width = '44px'// 给 CSS 对象中的高重新赋值
样式(style)
通过获取到元素的 DOM,可以对上面的样式进行修改
const dom = document.getElementById("foo")
// 样式名称和 CSS 中的基本相同
dom.style.height = '200px'
// 将 CSS 中的 - 转换为后面的字母大写即可
dom.style.backgroundColor = 'magenta'
在 js 中,style 属性只能访问和修改行内样式,即 body 内,访问不到 style 属性中的样式。如果 css 中使用
!important
,会导致该样式无法通过 style 属性修改。
除了样式外,还提供了一些属性,这些属性是只读的
// 返回元素节点可见部分的宽高,一般为 content-box 的 width 和 height(带 padding)
Element.clientHeight
Element.clientWidth
// 返回元素节点左边框的宽度(border-left、border-top)返回整数
Element.clientLeft
Element.clientTop
// 返回元素节点包括滚动区域的高度和宽度
Element.scrollHeight
Element.scrollWidth
// 返回元素节点的向右滚动和向下滚动的像素数值,设置属性可以改变元素的滚动位置
Element.scrollLeft
Element.scrollTop
// 返回最靠近当前元素的、并且 CSS 的 position 属性不等于 static 的上层元素
Element.offsetParent
// 返回元素的垂直高度(包含 border,padding)
Element.offsetHeight
Element.offsetWidth
Element.offsetLeft// 返回当前元素左上角相对于 Element.offsetParent 节点的垂直偏移
Element.offsetTop //返回水平位移
计算后的样式(画面中渲染的样式)
属性(attribute)
<div class="foo" id="box"></div>
getAttribute
:获取元素节点指定属性的值setAttribute
:创建或者改变元素节点的属性removeAttribute
:移除属性
// 通过 attribute 可以改变许多值
// 访问自定义名字
var oDiv = document.getElementById("id")
// 获取特定属性
oDiv.attributes["name"]
oDiv.getAttribute("id")
// 设置属性名
oDiv.setAttribute("title",'box1')
// 删除该属性,以及属性名
oDiv.removeAttribute("title")
// class 也可以通过 className 获取和修改
oDiv.className // classList 获取类名数组
// 获取大写的标签名
oDiv.tagName
// 元素的方向
oDiv.dir
// 元素是否可以拖动
oDiv.draggable
// 内部 HTML,可以修改
oDiv.innerHTML
// 整体 HTML,可修改
oDiv.outerHTML
// 是否设置了 contentEditable
oDiv.contentEditable
通用节点属性
当获取的 DOM 元素有很多子节点时,可以使用这些节点指针查找节点的附近节点
单个节点的指针
firstChild
元素子节点的第一个lastChild
元素子节点的最后一个previousSibling
、nextSibling
当前节点的前一个节点、后一个节点(包括文本节点)parentNode
当前节点的父节点firstElementChild
、lastElementChild
元素的第一个,最后一个元素节点previousSibling
、nextSibling
当前节点的前一个、后一个兄弟元素节点(包括文本节点)
多个节点指针
childNodes
获取元素子节点列表children
获取不包括文本节点的所有节点
节点类型
nodeType | nodeName | nodeValue | |
---|---|---|---|
元素节点 | 1 | 标签名 | null |
属性节点 | 2 | 属性名 | 属性值 |
文本节点 | 3 | #text | 文本内容 |
其它 nodeType:注释 8 | 文档 9
文本节点:包括元素之间的空格,或者是回车在内的内容
getComputedStyle
,全局方法,获取浏览器计算后,渲染的最终样式
// 获取 dom
const oDiv = document.querySelector("#foo")
console.log(oDiv.style)
// style 中的名称,是 css 中,将 - 删除,并将 - 后的首字母大写即可
// css 中的 background-color -> backgroundColor
getComputedStyle(oDiv)["height"]
// 将两个方法通过函数封装起来就可以实现兼容所有浏览器
function getStyle(node,cssStyle){
return node.currentStyle ? node.currentStyle['height'] : getComputedStyle(oDiv["height"])
}
获元素当前位置
getBoundingClientRect()
//
const oDiv = document.querySelector("#foo")
oDiv.getBoundingClientRect()
// bottom: 203
// height: 34
// left: 863.5
// right: 1275.25
// top: 169
// width: 411.75
// x: 863.5
// y: 169
内容控制
通过 document 对象上的内容去创建和还被
document.createElement()
创建节点document.createAttribute()
创建属性节点document.createTextNode()
创建文本节点
// 根据标签名,创建对应的节点,该节点拥有对应的方法,和属性
const tag_A = document.createElement('a')
tag_A.link = 'http://www.google.com'
document.createAttribute()
// 创建 attribute
const attr = document.createAttribute("my_attrib")
attr.value = "newVal"
tag_A.setAttributeNode(a)
const text = document.createTextNode()
tag_A.appendChild( document.createTextNode('as we can') )
cloneNode
复制节点
let oDiv = document.querySelector(".classic")
let newNode = oDiv.cloneNode(true) // true 表示会深克隆所有节点后代
插入节点
appendChild(node)
向子节点的最后添加指定节点append(string)
把 string 插入到文本末尾处insertBefore(newNode,referenceNode)
当前节点前面插入节点
替换节点
replaceChild(newChild, oldChild)
用前者替换后者节点
删除节点
removeChild()
删除指定节点remove()
删除节点自身
let oDiv = document.querySelector(".classic")
const tag_a = document.createElement('a')
const tag_span = document.createElement('span')
const tag_div = document.createElement('div')
tag_a.innerText = 'Fall'
tag_span.innerText = 'span 标签,知行合一'
tag_span.append('645645454')
tag_div.innerText = 'div 标签'
// 插入 a 标签
oDiv.appendChild(tag_a) // oDiv 最后插入 a 标签
oDiv.insertBefore(tag_span,tag_a) // 在 a 标签之前,插入 span
// 将 span 标签替换为 div 标签
oDiv.replaceChild(tag_div,tag_span)
// 删除 a 标签
oDiv.removeChild(tag_a)
// 或者使用 tag_a.remove() 移除自身
注意事项:
因为 JS 执行速度比对 DOM 操作执行的快很多。文档碎片操作(多次对 DOM 执行操作)时,可以通过一次性将文档插入到页面中的操作,进而节省 DOM 渲染时间,大多数框架会实现该内容。
document 的属性
- 全局容器属性
document.documentElement.clientWidth
:全局容器宽度document.body.clientWidth
:当前容器宽度document.docuemtElement.scrollTop
:当前容器顶端高度document.body.scrollTop
:当前容器顶端高度
oDiv.offsetleft
oDiv.offsetTop
// 获取当前可视区域距离第一个有定位的父节点的位置
getComputedStyle(oDiv)[width]
// 获取的仅为容器height设置的宽度
获取当前 document 容器的宽度
var width = document.documentElement.clientWidth;
var width = document.body.clientWidth;
const rectPosition = oDiv.getBoundingClientRect()
rectPosition.x // 距离最左侧 x 的距离等价于 left
rectPosition.y // 距离最顶部 y 的距离等价于 top
rectPosition.bottom // rect 最底部和 body 顶部的距离
rectPosition.right // rect 最右侧和 body 左侧的距离
特殊 DOM 属性
video
// 控制播放速率
$0.playbackRate = 2
// 暂停
$0.pause()
// 播放
$0.play()
// 循环控制
$0.loop = true
事件
Event(事件)
事件对象,所有的事件都继承了该对象
EventTarget(事件目标对象)Element,document,window 是最常见的事件目标
addEventListener()
添加事件removeEventListener()
移除事件dispatchEvent()
触发事件
任何事件目标都会实现与该接口有关的这三个方法。
const event = new Event("build");
// 监听该事件。
elem.addEventListener(
"build", (e) => {
/* … */
},
false,
);
// 分派该事件。
elem.dispatchEvent(event);
鼠标事件
MouseEvent(鼠标事件)
以下事件触发时,会调用 MouseEvent 事件
事件 | 名称 |
---|---|
click | 单击(鼠标按下,弹起整个过程结束后,触发点击事件) |
dblclick | 双击 |
mouseover | 鼠标移入(移动到子节点上也会触发移出) |
mouseout | 鼠标移出(移动到子节点上也会触发移出) |
mousemove | 鼠标移动时触发(在当前 DOM 上移动时,会不停的触发) |
mouseup | 鼠标抬起 |
mousedown | 鼠标按下 |
mouseenter | 鼠标移入(经过子节点不会重复触发) |
mouseleave | 鼠标移出(经过子节点不会重复触发) |
wheel | 鼠标的滚轮旋转 |
scroll | 滚动(内容区滚动的事件)(不算鼠标事件,为了和 wheel 对比,写在了这里 |
事件属性
function click(ev){
ev.button // 返回值 0,1,2,分别为鼠标左键滚轮和右键的点击
// 鼠标位置
ev.clientX
}
鼠标位置
clientX
、clinentY
:在可视区域的位置(以 DOM 为基准的像素位置)pageX
、pageY
:当前页面的位置(滚轮滑动会改变值)screenX
、screenY
:当前电脑显示器的横纵位置(窗口不是最大化的时候,移动会改变,以整个电脑屏幕为基准)
document.onmousedown = function(ev){
var e = ev || window.event;
alert(e.button);
}
var oDiv = document.getElementById('box');
oDiv.offsetLeft;
键盘事件
KeyboardEvent(键盘事件)
事件名称 | 事件效果 |
---|---|
keypress | 只有按字符键和方向键有效 |
keydown | 键盘按下时触发,并且长按功能键不会连续触发 |
keyup | 键盘抬起来时触发 |
shiftKey
、altKey
、ctrlKey
(win)、metaKey
(mac)如果按下对应按键,则值为 truecode
键码,区分是哪个按键(KeyA、ControlLeft)- key 按键的值,区分大小写,输出的值(a、Control、A)
- 浏览器为了兼容还实现了一些其它键盘属性,但是不建议使用。
document.onclick = function(ev){
if(ev.ctrlKey){
console.log('ctrlyes')
}
}
window.onkeydown = function(ev){
var e = ev||window.event; //对获取方法进行兼容
var which = e.which||e.keyCode; //键码操作的兼容
alert(which)
}
触摸屏事件
事件名称 | 事件效果 |
---|---|
touchcancel | 中断 touch,例如:创建太多触点 |
touchstart | 一个或多个触点与触控设备表面接触时被触发。 |
touchend | 结束触碰时触发 |
touchmove | 触点在平面上移动的时候触发 |
属性
targetTouches
changedTouches
touches
浏览器默认事件
事件名称 | 事件效果 |
---|---|
copy | 复制的时候触发(ctrl + c) |
cut | 剪切的时候触发 |
contextmenu | 鼠标右键菜单事件 |
取消浏览器默认右键菜单
document.onload = function(){
document.oncontextmenu = function(){
return false;
}
}
阻止浏览器点击 a 链接时弹出窗口
function preDef(ev){
if(ev.preventDefault){
ev.preventDefault();
}
}
window 事件
load
:当前页面加载完后触发unload
:当前页面解构的时候scroll
:页面滚动的时候触发resize
:窗口大小发生变化触发
document 事件
-
accept=".png, .jpg, .jpeg"
-
DOMContentLoaded 事件会在所有延迟加载的 JavaScript 脚本都执行完毕后触发。无需等待样式表、图像和子框架的完全加载。
-
visibilitychange DOM 的可见性更改时,将会触发该事件
document.addEventListener('DOMContentLoaded',function(){
console.log('DOMContentLoaded!');
});
for(var i=0; i<1000000000; i++){
// 这个同步脚本将延迟 DOM 的解析。
// 所以 DOMContentLoaded 事件稍后将启动。
}
// visibilitychange
addEventListener('visibilitychange',(ev)=>{
console.log(document.visibilityState)
if (document.hidden) {
audio.pause(); // 当窗口隐藏时,停止播放
} else {
audio.play();
}
})
element 事件
- contextmenu 打开上下文菜单的时候触发
- 鼠标事件
- 键盘事件
- 浏览器默认事件
scrollIntoView()
将当前元素滚动进入浏览器可见区域
const element = document.querySelector('.content')
element.scrollIntoView()
表单事件
form 表单事件
事件名称 | 事件效果 |
---|---|
blur | 失去焦点时触发 |
focus | 获取表单焦点时触发 |
focusout | 失去焦点触发 |
select | 输入框内文本选中时触发 |
change | 当对输入框内容进行修改,并且退出编辑时时触发 |
oDiv= document.getElementById('box')
oDiv.onchange = function(){
this.style.background = 'cyan'
}
必须添加在 form 元素上
- submit,点击
<input type="submit">
上的按钮才会触发 - reset,点击
<input type="reset">
按钮才会触发
获取 DOM 事件
getEventListeners:只有在调试的时候可以使用
const elementEvents = getEventListeners(element)
// 当前的点击事件
elementEvents.click
事件特性
target
触发对象是指 target 属性,只存在事件对象中,并且指向触发该事件的子容器
window.onclick = function(ev){
var e = ev||window.event;
var target = ev.target;
alert(target.innerHTML)
}
事件冒泡和事件捕获
- 事件冒泡:触发该事件的时候,会像冒泡一样,逐渐向上,触发父节点的事件
- 事件捕获:和事件冒泡相反,会从最外层的事件,逐渐触发,直到触发当前事件的 DOM
解决的方法
oli.onclick = function(ev){
ev.cancelBubble = true; // 取消事件冒泡
ev.stopPropagation(); // 方法阻止捕获和冒泡阶段中当前事件的进一步传播。
}
事件监听器
一般会通过事件监听器的方式添加事件,可以更精确的控制事件,并且附加的事件处理程序而不会覆盖已有的事件处理程序,因此可以为一个元素添加多个事件。
如果循环去添加一个匿名函数到事件监听器中,每一次都会添加新的事件,因此需要注意内存问题。或移除事件。
node.addEventListener(type,listener,options&useCapture)
第三个参数可以为 true 或者 false,可以表明
- true(事件捕获):从父节点到子节点
- false(事件冒泡):从子节点到父节点(默认)
也可以为一个配置的对象
capture
布尔值,表示listener
会在该类型的事件捕获阶段传播到该EventTarget
时触发。once
布尔值,表示listener
在添加之后最多只调用一次。如果为true
,listener
会在其被调用之后自动移除。passive
布尔值,设置为true
时,表示listener
永远不会调用preventDefault()
。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。signal
可选AbortSignal
,该AbortSignal
的abort()
方法被调用时,监听器会被移除。
// 示例
oBtn.addEventListener('click',handleClick,false)
function handleClick(ev){
console.log('点击了 button')
}
removeEventListener()
- 第一个参数 事件类型
- 第二个参数 删除的函数名称
只删除某一个函数而不影响其他的函数
oBtn.addEventListener('click',function(){
console.log('点击');
},false);
function show(){
console.log('强制')
}
oBtn.addEventListener('click',show,false);
oBtn.removeEcentListener('click',show);
事件委托
应用场景
如果使用 for 循环给节点添加事件,有一百个节点,使用一百个事件,浪费资源
实现步骤
- 找到当前节点的父节点或者祖先节点
- 将事件添加到父节点或者是祖先节点
- 找到触发对象,是否为想要的触发对象,然后进行操作
window.onload = function(){
var btn = document.getElementById('btn');
var list = document.getElementById('list');
var i = 0;
//点击则新增节点
btn.onclick = function(){
var nList = document.createElement('li');//每次循环都要新建一个节点
alert(i);
nList.innerHTML =((i++)+5)*1111;
list.appendChild(nList);
}
list.onclick =function(ev){
ev.target.style.backgroundColor = 'red';
}
}
dispatchEvent
用于触发事件,也可以是自定义事件
// 触发一次 input 标签上的事件
input.dispatchEvent(
// 第一个参数是触发事件的名称
new Event("input", {
bubbles: true,
cancelable: true
})
);